---
category: Level 3 — Advanced
created: '2020-03-08'
keywords:
title: Sort a table by clicking its headers
---

Assume that we want to sort any column of the following table:

```html
<table id="sortMe" class="table">
    ...
</table>
```

## Sort rows

First of all, we [query](https://phuoc.ng/collection/html-dom/select-an-element-or-list-of-elements/) all the headers, [loop over](https://phuoc.ng/collection/html-dom/loop-over-a-nodelist/) them and [attach an event handler](https://phuoc.ng/collection/html-dom/attach-or-detach-an-event-handler/) for the `click` event to each of them:

```js
// Query the table
const table = document.getElementById('sortMe');

// Query the headers
const headers = table.querySelectorAll('th');

// Loop over the headers
[].forEach.call(headers, function (header, index) {
    header.addEventListener('click', function () {
        // This function will sort the column
        sortColumn(index);
    });
});
```

The `sortColumn(index)` function mentioned above will sort all rows by given column `index`. To do so:

-   We can use the `Array`'s `sort()` method to sort the current rows
-   Then, [remove](https://phuoc.ng/collection/html-dom/remove-an-element/) all the current rows
-   And [append](https://phuoc.ng/collection/html-dom/append-to-an-element/) the sorted rows

```js
// Query all rows
const tableBody = table.querySelector('tbody');
const rows = tableBody.querySelectorAll('tr');

const sortColumn = function (index) {
    // Clone the rows
    const newRows = Array.from(rows);

    // Sort rows by the content of cells
    newRows.sort(function (rowA, rowB) {
        // Get the content of cells
        const cellA = rowA.querySelectorAll('td')[index].innerHTML;
        const cellB = rowB.querySelectorAll('td')[index].innerHTML;

        switch (true) {
            case cellA > cellB:
                return 1;
            case cellA < cellB:
                return -1;
            case cellA === cellB:
                return 0;
        }
    });

    // Remove old rows
    [].forEach.call(rows, function (row) {
        tableBody.removeChild(row);
    });

    // Append new row
    newRows.forEach(function (newRow) {
        tableBody.appendChild(newRow);
    });
};
```

As you see, an array provides a built-in `sort` method which accepts a function to compare two items. In our case, two cells of the column are compared based on its [HTML content](https://phuoc.ng/collection/html-dom/get-or-set-the-html-of-an-element/):

```js
newRows.sort(function(rowA, rowB) {
    // Get the content of cells
    const cellA = rowA.querySelectorAll('td')[index].innerHTML;
    const cellB = rowB.querySelectorAll('td')[index].innerHTML;

    ...
});
```

It works well with the cells whose content are string, not numbers or another type such as date. Going to the next section to see how we can support those cases.

## Support other types

We add a custom attribute to each header to indicate the type of its cells:

```html
<thead>
    <tr>
        <th data-type="number">No.</th>
        <th>First name</th>
        <th>Last name</th>
    </tr>
</thead>
```

For example, the _No._ column would have a `data-type="number"` attribute. If the attribute is missing, the content types of cells are string. We need a function to transform the content of cells from string to another type:

```js
// Transform the content of given cell in given column
const transform = function (index, content) {
    // Get the data type of column
    const type = headers[index].getAttribute('data-type');
    switch (type) {
        case 'number':
            return parseFloat(content);
        case 'string':
        default:
            return content;
    }
};
```

The sample code demonstrates the `number` and `string` columns, but you are free to support more types such as date.

Now we improve the `sortColumn` function a little bit to support the custom content types. Instead of comparing the raw content, we compare the values which are converted based on the content type:

```js
newRows.sort(function (rowA, rowB) {
    const cellA = rowA.querySelectorAll('td')[index].innerHTML;
    const cellB = rowB.querySelectorAll('td')[index].innerHTML;

    // Transform the content of cells
    const a = transform(index, cellA);
    const b = transform(index, cellB);

    // And compare them
    switch (true) {
        case a > b:
            return 1;
        case a < b:
            return -1;
        case a === b:
            return 0;
    }
});
```

## Support both directions

At the moment, clicking a header sorts all the rows. We should reverse the direction if user clicks the header again. To do so, we prepare a variable to manage the sorting directions of all headers:

```js
// Track sort directions
const directions = Array.from(headers).map(function (header) {
    return '';
});
```

`directions` is an array which each item can be either `asc` or `desc` indicating the sorting direction in the associate column. The `sortColumn()` function now involves more logics to compare two rows based on the current direction:

```js
const sortColumn = function(index) {
    // Get the current direction
    const direction = directions[index] || 'asc';

    // A factor based on the direction
    const multiplier = (direction === 'asc') ? 1 : -1;

    ...

    newRows.sort(function(rowA, rowB) {
        const cellA = rowA.querySelectorAll('td')[index].innerHTML;
        const cellB = rowB.querySelectorAll('td')[index].innerHTML;

        const a = transform(index, cellA);
        const b = transform(index, cellB);

        switch (true) {
            case a > b: return 1 * multiplier;
            case a < b: return -1 * multiplier;
            case a === b: return 0;
        }
    });

    ...

    // Reverse the direction
    directions[index] = direction === 'asc' ? 'desc' : 'asc';

    ...
};
```

## Demo

<Playground>
```html
<table id="sortMe" class="table">
    <thead>
        <tr>
            <th data-type="number"><button class="table__header">No.</button></th>
            <th><button class="table__header">First name</button></th>
            <th><button class="table__header">Last name</button></th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>1</td>
            <td>Andrea</td>
            <td>Ross</td>
        </tr>
        <tr>
            <td>2</td>
            <td>Penelope</td>
            <td>Mills</td>
        </tr>
        <tr>
            <td>3</td>
            <td>Sarah</td>
            <td>Grant</td>
        </tr>
        <tr>
            <td>4</td>
            <td>Vanessa</td>
            <td>Roberts</td>
        </tr>
        <tr>
            <td>5</td>
            <td>Oliver</td>
            <td>Alsop</td>
        </tr>
        <tr>
            <td>6</td>
            <td>Jennifer</td>
            <td>Forsyth</td>
        </tr>
        <tr>
            <td>7</td>
            <td>Michelle</td>
            <td>King</td>
        </tr>
        <tr>
            <td>8</td>
            <td>Steven</td>
            <td>Kelly</td>
        </tr>
        <tr>
            <td>9</td>
            <td>Julian</td>
            <td>Ferguson</td>
        </tr>
        <tr>
            <td>10</td>
            <td>Chloe</td>
            <td>Ince</td>
        </tr>
    </tbody>
</table>
```

```css
.table {
    border-collapse: collapse;
}
.table__header {
    background-color: transparent;
    border: none;
    cursor: pointer;
}
.table,
.table th,
.table td {
    border: 1px solid #ccc;
}
.table th,
.table td {
    padding: 0.5rem;
}
.table th {
    cursor: pointer;
    text-decoration: underline;
}
```

```js
document.addEventListener('DOMContentLoaded', function () {
    const table = document.getElementById('sortMe');
    const headers = table.querySelectorAll('th');
    const tableBody = table.querySelector('tbody');
    const rows = tableBody.querySelectorAll('tr');

    // Track sort directions
    const directions = Array.from(headers).map(function (header) {
        return '';
    });

    // Transform the content of given cell in given column
    const transform = function (index, content) {
        // Get the data type of column
        const type = headers[index].getAttribute('data-type');
        switch (type) {
            case 'number':
                return parseFloat(content);
            case 'string':
            default:
                return content;
        }
    };

    const sortColumn = function (index) {
        // Get the current direction
        const direction = directions[index] || 'asc';

        // A factor based on the direction
        const multiplier = direction === 'asc' ? 1 : -1;

        const newRows = Array.from(rows);

        newRows.sort(function (rowA, rowB) {
            const cellA = rowA.querySelectorAll('td')[index].innerHTML;
            const cellB = rowB.querySelectorAll('td')[index].innerHTML;

            const a = transform(index, cellA);
            const b = transform(index, cellB);

            switch (true) {
                case a > b:
                    return 1 * multiplier;
                case a < b:
                    return -1 * multiplier;
                case a === b:
                    return 0;
            }
        });

        // Remove old rows
        [].forEach.call(rows, function (row) {
            tableBody.removeChild(row);
        });

        // Reverse the direction
        directions[index] = direction === 'asc' ? 'desc' : 'asc';

        // Append new row
        newRows.forEach(function (newRow) {
            tableBody.appendChild(newRow);
        });
    };

    [].forEach.call(headers, function (header, index) {
        header.addEventListener('click', function () {
            sortColumn(index);
        });
    });
});
```
</Playground>

## See also

-   [Append to an element](https://phuoc.ng/collection/html-dom/append-to-an-element/)
-   [Attach or detach an event handler](https://phuoc.ng/collection/html-dom/attach-or-detach-an-event-handler/)
-   [Get or set the html of an element](https://phuoc.ng/collection/html-dom/get-or-set-the-html-of-an-element/)
-   [Get set and remove data attributes](https://phuoc.ng/collection/html-dom/get-set-and-remove-data-attributes/)
-   [Loop over a nodelist](https://phuoc.ng/collection/html-dom/loop-over-a-nodelist/)
-   [Remove an element](https://phuoc.ng/collection/html-dom/remove-an-element/)
-   [Select an element or list of elements](https://phuoc.ng/collection/html-dom/select-an-element-or-list-of-elements/)
